/*
 * lpack.c
 * a Lua library for packing and unpacking binary data
 * Luiz Henrique de Figueiredo <lhf@tecgraf.puc-rio.br>
 * 29 Jun 2007 19:27:20
 * This code is hereby placed in the public domain.
 * with contributions from Ignacio Casta�o <castanyo@yahoo.es> and
 * Roberto Ierusalimschy <roberto@inf.puc-rio.br>.
 */
#include <ctype.h>
#include <string.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"


unsigned int ZigZagEncode32(int n)
{
	return (n << 1) ^ (n >> 31);
}

int ZigZagDecode32(unsigned int n)
{
return(n >> 1) ^ -(int)(n & 1);
}

unsigned long long ZigZagEncode64(long long n)
{
	return (n << 1) ^ (n >> 63);
}

long long ZigZagDecode64(unsigned long long n)
{
return(n >> 1) ^ -(long)(n & 1);
}

unsigned char* GLNReadLEN(unsigned char* buf, int limit, unsigned int* value)
{
	unsigned char* ptr = buf;
	unsigned int b;
	unsigned int result;
	if (0 > limit--)
		return NULL;
	b = *(ptr++);
	result = (b & 0x7F);
	if (!(b & 0x80))
		goto done;
	if (0 > limit--)
		return NULL;
	b = *(ptr++);
	result |= (b & 0x7F) << 7;
	if (!(b & 0x80))
		goto done;
	if (0 > limit--)
		return NULL;
	b = *(ptr++);
	result |= (b & 0x7F) << 14;
	if (!(b & 0x80))
		goto done;
	if (0 > limit--)
		return NULL;
	b = *(ptr++);
	result |= (b & 0x7F) << 21;
	if (!(b & 0x80))
		goto done;
	if (0 > limit--)
		return NULL;
	b = *(ptr++);
	result |= b << 28;
	if (!(b & 0x80))
		goto done;
	return NULL;

	done: *value = result;
	return ptr;
}
unsigned char* GLNReadVarInt(unsigned char* buf, int limit, int* value)
{
	unsigned int v;
	buf = GLNReadLEN(buf, limit, &v);
	if (buf)
	{
		*value = ZigZagDecode32(v);
		return buf;
	}
	return NULL;
}

//////////////////////////////////////////////////////////////////////////
//buf len must>=5
unsigned char* GLNWriteLEN(unsigned char* buf, unsigned int value)
{
	unsigned char* target = (unsigned char*) buf;
	target[0] = (unsigned char) (value | 0x80);
	if (value >= (1 << 7))
	{
		target[1] = (unsigned char) ((value >> 7) | 0x80);
		if (value >= (1 << 14))
		{
			target[2] = (unsigned char) ((value >> 14) | 0x80);
			if (value >= (1 << 21))
			{
				target[3] = (unsigned char) ((value >> 21) | 0x80);
				if (value >= (1 << 28))
				{
					target[4] = (unsigned char) (value >> 28);
					return target + 5;
				}
				else
				{
					target[3] &= 0x7F;
					return target + 4;
				}
			}
			else
			{
				target[2] &= 0x7F;
				return target + 3;
			}
		}
		else
		{
			target[1] &= 0x7F;
			return target + 2;
		}
	}
	else
	{
		target[0] &= 0x7F;
		return target + 1;
	}
}
unsigned char* GLNWriteVarInt(unsigned char* buf, int value)
{
	return GLNWriteLEN(buf, ZigZagEncode32(value));
}

#define USE_GLOBALS
#define	OP_ASTRING	'j'		/* asn len string */
#define	OP_ALEN	'e'		/* asn len (unsigned int)*/
#define	OP_AINT	'E'		/* asn int (int)*/

#define	OP_ZSTRING	'z'		/* zero-terminated string */
#define	OP_BSTRING	'p'		/* string preceded by length byte */
#define	OP_WSTRING	'P'		/* string preceded by length word */
#define	OP_SSTRING	'a'		/* string preceded by length size_t */
#define	OP_STRING	'A'		/* string */
#define	OP_FLOAT	'f'		/* float */
#define	OP_DOUBLE	'd'		/* double */
#define	OP_NUMBER	'n'		/* Lua number */
#define	OP_CHAR		'c'		/* char */
#define	OP_BYTE		'b'		/* byte = unsigned char */
#define	OP_SHORT	'h'		/* short */
#define	OP_USHORT	'H'		/* unsigned short */
#define	OP_INT		'i'		/* int */
#define	OP_UINT		'I'		/* unsigned int */
#define	OP_LONG		'l'		/* long */
#define	OP_ULONG	'L'		/* unsigned long */
#define	OP_LITTLEENDIAN	'<'		/* little endian */
#define	OP_BIGENDIAN	'>'		/* big endian */
#define	OP_NATIVE	'='		/* native endian */


static void badcode(lua_State *L, int c)
{
	char s[] = "bad code `?'";
	s[sizeof(s) - 3] = c;
	luaL_argerror(L, 1, s);
}

static int doendian(int c)
{
	int x = 1;
	int e = *(char*) &x;
	if (c == OP_LITTLEENDIAN)
		return !e;
	if (c == OP_BIGENDIAN)
		return e;
	if (c == OP_NATIVE)
		return 0;
	return 0;
}

static void doswap(int swap, void *p, size_t n)
{
	if (swap)
	{
		char *a = p;
		int i, j;
		for (i = 0, j = n - 1, n = n / 2; n--; i++, j--)
		{
			char t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
	}
}

#define UNPACKNUMBER(OP,T)		\
   case OP:				\
   {					\
    T a;				\
    int m=sizeof(a);			\
    if (i+m>len) goto done;		\
    memcpy(&a,s+i,m);			\
    i+=m;				\
    doswap(swap,&a,m);			\
    lua_pushnumber(L,(lua_Number)a);	\
    ++n;				\
    break;				\
   }

#define UNPACKSTRING(OP,T)		\
   case OP:				\
   {					\
    T l;				\
    int m=sizeof(l);			\
    if (i+m>len) goto done;		\
    memcpy(&l,s+i,m);			\
    doswap(swap,&l,m);			\
    if (i+m+l>len) goto done;		\
    i+=m;				\
    lua_pushlstring(L,s+i,l);		\
    i+=l;				\
    ++n;				\
    break;				\
   }

static int l_unpack(lua_State *L) /** unpack(s,f,[init]) */
{
	size_t len;
	const char *s = luaL_checklstring(L, 1, &len);
	const char *f = luaL_checkstring(L, 2);
	int i = luaL_optnumber(L, 3, 1) - 1;
	int n = 0;
	int swap = 0;
	lua_pushnil(L);
	while (*f)
	{
		int c = *f++;
		int N = 1;
		if (isdigit(*f))
		{
			N = 0;
			while (isdigit(*f))
				N = 10* N + (*f++) - '0';
			if (N == 0 && c == OP_STRING)
			{
				lua_pushliteral(L, "");
				++n;
			}
		}
		while (N--)
			switch (c)
			{
			case OP_LITTLEENDIAN:
			case OP_BIGENDIAN:
			case OP_NATIVE:
			{
				swap = doendian(c);
				N = 0;
				break;
			}
			case OP_STRING:
			{
				++N;
				if (i+N < i || i + N > len)
					goto done;
				lua_pushlstring(L, s + i, N);
				i += N;
				++n;
				N = 0;
				break;
			}
			case OP_ZSTRING:
			{
				size_t l;
				if (i >= len)
					goto done;
				l = strlen(s + i);
				lua_pushlstring(L, s + i, l);
				i += l + 1;
				++n;
				break;
			}
			case OP_ASTRING:
			{
				unsigned int l;
				int m;
				char* next = GLNReadLEN(s + i, len - i, &l);
				if (!next)
					goto done;
				m = next - s - i;
				if (i + m + l > len)
					goto done;
				i += m;
				lua_pushlstring(L, s + i, l);
				i += l;
				++n;
				break;
			}
			case OP_ALEN:
			{
				unsigned int l;
				int m;
				char* next = GLNReadLEN(s + i, len - i, &l);
				if (!next)
					goto done;
				m = next - s - i;
				i += m;
				lua_pushnumber(L, (lua_Number) l);
				++n;
				break;
			}
			case OP_AINT:
			{
				int m;
				int l;
				char* next = GLNReadVarInt(s + i, len - i, &l);
				if (!next)
					goto done;
				m = next - s - i;
				i += m;
				lua_pushnumber(L, (lua_Number) l);
				++n;
				break;
			}
			UNPACKSTRING(OP_BSTRING, unsigned char)
			UNPACKSTRING(OP_WSTRING, unsigned short)
			UNPACKSTRING(OP_SSTRING, size_t)
			UNPACKNUMBER(OP_NUMBER, lua_Number)
			UNPACKNUMBER(OP_DOUBLE, double)
			UNPACKNUMBER(OP_FLOAT, float)
			UNPACKNUMBER(OP_CHAR, char)
			UNPACKNUMBER(OP_BYTE, unsigned char)
			UNPACKNUMBER(OP_SHORT, short)
			UNPACKNUMBER(OP_USHORT, unsigned short)
			UNPACKNUMBER(OP_INT, int)
			UNPACKNUMBER(OP_UINT, unsigned int)
			UNPACKNUMBER(OP_LONG, long)
			UNPACKNUMBER(OP_ULONG, unsigned long)
			case ' ':
			case ',':
				break;
			default:
				badcode(L, c);
				break;
			}
	}
	done: lua_pushnumber(L, i + 1);
	lua_replace(L, -n - 2);
	return n + 1;
}

#define PACKNUMBER(OP,T)			\
   case OP:					\
   {						\
    T a=(T)luaL_checknumber(L,i++);		\
    doswap(swap,&a,sizeof(a));			\
    luaL_addlstring(&b,(void*)&a,sizeof(a));	\
    break;					\
   }

#define PACKSTRING(OP,T)			\
   case OP:					\
   {						\
    size_t l;					\
    const char *a=luaL_checklstring(L,i++,&l);	\
    T ll=(T)l;					\
    doswap(swap,&ll,sizeof(ll));		\
    luaL_addlstring(&b,(void*)&ll,sizeof(ll));	\
    luaL_addlstring(&b,a,l);			\
    break;					\
   }

static int l_pack(lua_State *L) /** pack(f,...) */
{
	int i = 2;
	const char *f = luaL_checkstring(L, 1);
	int swap = 0;
	luaL_Buffer b;
	luaL_buffinit(L, &b);
	while (*f)
	{
		int c = *f++;
		int N = 1;
		if (isdigit(*f))
		{
			N = 0;
			while (isdigit(*f))
				N = 10* N + (*f++) - '0';
		}
		while (N--)
			switch (c)
			{
			case OP_LITTLEENDIAN:
			case OP_BIGENDIAN:
			case OP_NATIVE:
			{
				swap = doendian(c);
				N = 0;
				break;
			}
			case OP_STRING:
			case OP_ZSTRING:
			{
				size_t l;
				const char *a = luaL_checklstring(L, i++, &l);
				luaL_addlstring(&b, a, l + (c == OP_ZSTRING));
				break;
			}
			case OP_ASTRING:
			{
				size_t l;
				unsigned char buf[8];
				unsigned char* next;
				const char *a = luaL_checklstring(L, i++, &l);
				next = GLNWriteLEN(buf, l);
				luaL_addlstring(&b, (void*) buf, next - buf);
				luaL_addlstring(&b, a, l);
				break;
			}
			case OP_ALEN:
			{
				unsigned char buf[8];
				unsigned char* next;
				unsigned int a = (unsigned int) luaL_checknumber(L, i++);
				next = GLNWriteLEN(buf, a);
				luaL_addlstring(&b, (void*) buf, next - buf);
				break;
			}
			case OP_AINT:
			{
				unsigned char buf[8];
				unsigned char* next;
				int a = (int) luaL_checknumber(L, i++);
				next = GLNWriteVarInt(buf, a);
				luaL_addlstring(&b, (void*) buf, next - buf);
				break;
			}
			PACKSTRING(OP_BSTRING, unsigned char)
			PACKSTRING(OP_WSTRING, unsigned short)
			PACKSTRING(OP_SSTRING, size_t)
			PACKNUMBER(OP_NUMBER, lua_Number)
			PACKNUMBER(OP_DOUBLE, double)
			PACKNUMBER(OP_FLOAT, float)
			PACKNUMBER(OP_CHAR, char)
			PACKNUMBER(OP_BYTE, unsigned char)
			PACKNUMBER(OP_SHORT, short)
			PACKNUMBER(OP_USHORT, unsigned short)
			PACKNUMBER(OP_INT, int)
			PACKNUMBER(OP_UINT, unsigned int)
			PACKNUMBER(OP_LONG, long)
			PACKNUMBER(OP_ULONG, unsigned long)
			case ' ':
			case ',':
				break;
			default:
				badcode(L, c);
				break;
			}
	}
	luaL_pushresult(&b);
	return 1;
}

static int floatToHex(lua_State *L)
{
	float f=(float)luaL_checknumber(L,1);
	char buf[16];
	sprintf(buf, "%08X", *((int*)(&f)) );
	lua_pushlstring(L,buf,8);
	return 1;
}

static int hexToFloat(lua_State *L)
{
	const char* str=luaL_checkstring(L,1);
    int strLen = 8;//32 bit float
    unsigned char bytes[4];
    int bptr = (strLen / 2) - 1;
    int i;
    for (i = 0; i < strLen; i++){
        unsigned char   c;
        c = str[i];
        if (c > '9') c -= 7;
        c <<= 4;
        bytes[bptr] = c;

        ++i;
        c = str[i];
        if (c > '9') c -= 7;
        c -= '0';
        bytes[bptr] |= c;

        --bptr;
    }
    lua_pushnumber(L,*((float*)bytes));
    return 1;
}


static const luaL_reg R[] =
{
{ "pack", l_pack },
{ "unpack", l_unpack },
{"float2hex",floatToHex},
{"hex2float",hexToFloat},
{ NULL, NULL } };

int luaopen_zpack(lua_State *L)
{
#ifdef USE_GLOBALS
	lua_register(L, "zpack", l_pack);
	lua_register(L, "zunpack", l_unpack);
	lua_register(L, "float2hex", floatToHex);
	lua_register(L, "hex2float", hexToFloat);
#else
	luaL_openlib(L, LUA_STRLIBNAME, R, 0);
#endif
	return 0;
}
